<template>
  <Cascader
    v-model:value="state"
    :options="options"
    :load-data="loadData"
    change-on-select
    :displayRender="handleRenderDisplay"
    @change="handleChange"
  >
    <template v-if="loading" #suffixIcon>
      <LoadingOutlined spin />
    </template>
    <template v-if="loading" #notFoundContent>
      <span>
        <LoadingOutlined spin class="mr-1" />
        {{ t('component.form.apiSelectNotFound') }}
      </span>
    </template>
  </Cascader>
</template>
<script lang="ts" setup name="ApiCascader">
import { LoadingOutlined } from '@ant-design/icons-vue';
import { Cascader } from 'ant-design-vue';
import { get, omit } from 'lodash-es';
import { ref, unref, watch, watchEffect } from 'vue';

import { useRuleFormItem } from '@/hooks/component/useFormItem';
import { useI18n } from '@/hooks/web/useI18n';
import { isFunction } from '@/utils/is';
import { propTypes } from '@/utils/propTypes';

interface Option {
  value: string;
  label: string;
  loading?: boolean;
  isLeaf?: boolean;
  children?: Option[];
}

const props = defineProps({
  value: {
    type: Array,
  },
  api: {
    type: Function as PropType<(arg?: Recordable) => Promise<Option[]>>,
    default: null,
  },
  numberToString: propTypes.bool,
  resultField: propTypes.string.def(''),
  labelField: propTypes.string.def('label'),
  valueField: propTypes.string.def('value'),
  childrenField: propTypes.string.def('children'),
  asyncFetchParamKey: propTypes.string.def('parentCode'),
  immediate: propTypes.bool.def(true),
  // init fetch params
  initFetchParams: {
    type: Object as PropType<Recordable>,
    default: () => ({}),
  },
  // 是否有下级，默认是
  isLeaf: {
    type: Function as PropType<(arg: Recordable) => boolean>,
    default: null,
  },
  displayRenderArray: {
    type: Array,
  },
});
const emit = defineEmits(['change', 'defaultChange']);

const apiData = ref<any[]>([]);
const options = ref<Option[]>([]);
const loading = ref<boolean>(false);
const emitData = ref<any[]>([]);
const isFirstLoad = ref(true);
const { t } = useI18n();
// Embedded in the form, just use the hook binding to perform form verification
const [state]: any = useRuleFormItem(props, 'value', 'change', emitData);

watch(
  apiData,
  (data) => {
    const opts = generatorOptions(data);
    options.value = opts;
  },
  { deep: true },
);

function generatorOptions(options: any[]): Option[] {
  const { labelField, valueField, numberToString, childrenField, isLeaf } = props;
  return options.reduce((prev, next: Recordable) => {
    if (next) {
      const value = next[valueField];
      const item = {
        ...omit(next, [labelField, valueField]),
        label: next[labelField],
        value: numberToString ? `${value}` : value,
        isLeaf: isLeaf && typeof isLeaf === 'function' ? isLeaf(next) : false,
      };
      const children = Reflect.get(next, childrenField);
      if (children) {
        Reflect.set(item, childrenField, generatorOptions(children));
      }
      prev.push(item);
    }
    return prev;
  }, [] as Option[]);
}

async function initialFetch() {
  const api = props.api;
  if (!api || !isFunction(api)) return;
  apiData.value = [];
  loading.value = true;
  try {
    const res = await api(props.initFetchParams);
    if (Array.isArray(res)) {
      apiData.value = res;
      return;
    }
    if (props.resultField) {
      apiData.value = get(res, props.resultField) || [];
    }
  } catch (error) {
    console.warn(error);
  } finally {
    loading.value = false;
  }
}

async function loadData(selectedOptions: Option[]) {
  const targetOption = selectedOptions[selectedOptions.length - 1];
  targetOption.loading = true;

  const api = props.api;
  if (!api || !isFunction(api)) return;
  try {
    const res = await api({
      [props.asyncFetchParamKey]: Reflect.get(targetOption, 'value'),
    });
    if (Array.isArray(res)) {
      const children = generatorOptions(res);
      targetOption.children = children;
      return;
    }
    if (props.resultField) {
      const children = generatorOptions(get(res, props.resultField) || []);
      targetOption.children = children;
    }
  } catch (e) {
    console.error(e);
  } finally {
    targetOption.loading = false;
  }
}

watchEffect(() => {
  props.immediate && initialFetch();
});

watch(
  () => props.initFetchParams,
  () => {
    !unref(isFirstLoad) && initialFetch();
  },
  { deep: true },
);

function handleChange(keys, args) {
  emitData.value = keys;
  emit('defaultChange', keys, args);
}

function handleRenderDisplay({ labels, selectedOptions }) {
  if (unref(emitData).length === selectedOptions.length) {
    return labels.join(' / ');
  }
  if (props.displayRenderArray) {
    return props.displayRenderArray.join(' / ');
  }
  return '';
}
</script>
